package com.example.vsftpd.util;

import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
 * 运行外部程序。
 * @author yangjl
 * @since 2021-07-01
 */
public class RunProcessUtils {

    protected static Logger logger = LoggerFactory.getLogger(RunProcessUtils.class);

    /**
     * 执行简单命令（耗时3秒内，输出文本信息也不多的命令）。
     * @param dir 此目录作为程序运行的工作目录。如：/cpp/paixu/
     * @param cmd 命令集合。如：{ "sh", "-c", "awk '{print $4}' solutions > solutions_result.txt " }
     */
    public static final Map<String, String> execSimpleCmd(String dir, String[] cmd){

        Assert.hasText(dir, "program work dir is null.");
        Assert.notNull(cmd, "programFileName is null.");

        InputStream in = null;
        Process p = null;
        Runtime rt = Runtime.getRuntime();

        Map<String, String> responseMap = new HashMap<>(2);

        try {
            p = rt.exec(cmd, null, new File(dir));
            logger.debug("\n已调用：{}，等待返回结果......\n", Arrays.toString(cmd));
            in = p.getErrorStream();
            String responseError =  IOUtils.toString(in, "UTF-8") ;
            in.close();
            if(StringUtils.hasText(responseError)){
                logger.error("\n 外部程序返回错误：" + responseError);
                p.destroy();
                if(responseError.length()>2000){
                    // 数据库错误描述字段最大2000
                    responseError = responseError.substring(0, 1000);
                }
                throw new RuntimeException("外部程序（" + Arrays.toString(cmd) + "）返回错误：" + responseError);
            }

            in = p.getInputStream();
            String response =  IOUtils.toString(in, "UTF-8") ;
            if(logger.isDebugEnabled()){
                logger.debug("\n 外部程序返回：{}", response);
            }

            p.waitFor();

            logger.debug("\n p.waitFor() 执行结束，准备获取 p.exitValue ......");
            int i = p.exitValue();  //接收执行完毕的返回值
            if (i == 0) {
                logger.info("{} 执行成功！", Arrays.toString(cmd));
                responseMap.put("code", "0");
                responseMap.put("msg", response);
            } else {
                logger.error("{} 执行失败！", Arrays.toString(cmd));
                logger.error("\n" + responseError);
                responseMap.put("code", "-1");
                responseMap.put("msg", Arrays.toString(cmd) + " 执行失败！" + responseError);
            }
        } catch (Exception e) {
            logger.error("{} 执行失败！{}", Arrays.toString(cmd), e);
            throw new RuntimeException(e);
        } finally {
            try {
                if (in != null) {
                    in.close();
                }
                if (p != null) {
                    p.destroy();
                }
            } catch (IOException e) {
                logger.error("", e);
            }
        }
        return responseMap;
    }

    /**
     * 执行耗时命令。有些外部命令（如编写的shell脚本调用了其他耗时命令）执行时间长，输出信息多，要等待返回的结果，需要使用此方法。
     * @param dir 此目录作为程序运行的工作目录。如：/cpp/paixu/
     * @param cmd 命令集合。如：{ "sh", "-c", "awk '{print $4}' solutions > solutions_result.txt " }
     */
    public static final Map<String, String> execLongTimeCmd(String dir, String[] cmd){

        Assert.hasText(dir, "program work dir is null.");
        Assert.notNull(cmd, "programFileName is null.");

        InputStream in = null;
        Process p = null;
        Runtime rt = Runtime.getRuntime();

        Map<String, String> responseMap = new HashMap<>(2);

        try {
            p = rt.exec(cmd, null, new File(dir));
            logger.debug("\n已调用：{}，等待返回结果......\n", Arrays.toString(cmd));
            StreamGobbler errorGobbler = new StreamGobbler(p.getErrorStream(), "ERROR");

// kick off stderr
            errorGobbler.start();

            StreamGobbler outGobbler = new StreamGobbler(p.getInputStream(), "STDOUT");
// kick off stdout
            outGobbler.start();

            p.waitFor();

            logger.debug("\n p.waitFor() 执行结束，准备获取 p.exitValue ......");
            int i = p.exitValue();  //接收执行完毕的返回值
            if (i == 0) {
                logger.info("{} 执行成功！", Arrays.toString(cmd));
                responseMap.put("code", "0");
//                responseMap.put("msg", response);
            } else {
                logger.error("{} 执行失败！", Arrays.toString(cmd));
//                logger.error("\n" + responseError);
                responseMap.put("code", "-1");
                responseMap.put("msg", Arrays.toString(cmd) + " 执行失败！");
            }
        } catch (Exception e) {
            logger.error("{} 执行失败！{}", Arrays.toString(cmd), e);
            throw new RuntimeException(e);
        } finally {
            try {
                if (in != null) {
                    in.close();
                }
                if (p != null) {
                    p.destroy();
                }
            } catch (IOException e) {
                logger.error("", e);
            }
        }
        return responseMap;
    }

    /**
     * 执行某个程序。
     * @param dir 目标程序所在文件夹路径。同时也用此目录作为程序运行的工作目录。如：E:/cpp/paixu/
     * @param programFileName 目标程序名称。如：ConsoleApplication1.exe
     */
    public static final Map<String, String> execProgram(String dir, String programFileName){

        Assert.hasText(dir, "program work dir is null.");
        Assert.hasText(programFileName, "programFileName is null.");

        if(!dir.endsWith("/")){
            dir += "/";
        }
        StringBuffer cmd = new StringBuffer();
        cmd.append(dir).append(programFileName);

        InputStream in = null;
        Process p = null;
        Runtime rt = Runtime.getRuntime();

        Map<String, String> responseMap = new HashMap<>(2);
        try {
            p = rt.exec(cmd.toString(), null, new File(dir));
            logger.debug("\n已调用：{} ，等待返回结果......\n", cmd);

            in = p.getErrorStream();
            String responseError =  IOUtils.toString(in, "UTF-8") ;
            in.close();
            if(StringUtils.hasText(responseError)){
                logger.error("\n外部程序返回错误：" + responseError);
                p.destroy();
                if(responseError.length()>2000){
                    // 数据库错误描述字段最大2000
                    responseError = responseError.substring(0, 1000);
                }
                throw new RuntimeException("外部程序（" + programFileName + "）返回错误：" + responseError);
            }

            in = p.getInputStream();
            String response =  IOUtils.toString(in, "UTF-8") ;
            if(logger.isDebugEnabled()){
                logger.debug("\n 外部程序返回：{}", response);
            }
            p.waitFor();
            int i = p.exitValue();  //接收执行完毕的返回值
            if (i == 0) {
                logger.info("{} 执行成功！", programFileName);
                responseMap.put("code", "0");
                responseMap.put("msg", response);
            } else {
//                String error =  IOUtils.toString(p.getErrorStream(), "GBK") ;
                logger.error("{} 执行失败！{}", programFileName, responseError);
                responseMap.put("code", "-1");
                responseMap.put("msg", programFileName + " 执行失败！" + responseError);
            }
        } catch (Exception e) {
            logger.error("{} 执行失败！{}", programFileName, e);
            throw new RuntimeException(e);
        } finally {
            try {
                if (in != null) {
                    in.close();
                }
                if (p != null) {
                    p.destroy();
                }
            } catch (IOException e) {
                logger.error("", e);
            }
        }
        return responseMap;
    }

}
